Previous slide Next slide Toggle fullscreen Open presenter view
Программирование сетевых приложений
Потоки ввода/вывода и работа с файлами в C++ и Qt
Программирование сетевых приложений
Содержание лекции
Введение в потоки ввода/вывода
Консольный ввод и вывод
Работа с файлами в C++
Классы и интерфейсы потоков
Байтовые и символьные потоки
Буферизированные потоки
Потоки в Qt
Сериализация данных
Преимущества потоковой обработки
Практические примеры
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Введение в потоки ввода/вывода
Потоки ввода/вывода (I/O streams) являются фундаментальным механизмом для обработки данных в C++. Они предоставляют единообразный интерфейс для работы с различными источниками и приемниками данных: файлами, консолью, сетевыми соединениями и другими устройствами.
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Основные преимущества потоков
Унификация - единый интерфейс для различных типов данных
Безопасность - автоматическая обработка ошибок и исключений
Гибкость - легкость в расширении и настройке
Эффективность - оптимизированная буферизация и обработка
Типобезопасность - компилятор контролирует типы данных
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Иерархия потоков в C++
ios_base
├── ios
│ ├── istream
│ │ ├── ifstream (файлы)
│ │ ├── istringstream (строки)
│ │ └── iostream
│ ├── ostream
│ │ ├── ofstream (файлы)
│ │ ├── ostringstream (строки)
│ │ └── iostream
│ └── iostream
│ ├── fstream
│ └── stringstream
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Консольный ввод и вывод
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Базовые операции ввода/вывода
#include <iostream>
#include <string>
int main () {
std::cout << "Hello, World!" << std::endl;
std::cout << "Введите ваше имя: " ;
std::string name;
std::getline (std::cin, name);
int age = 25 ;
std::cout << "Привет, " << name << "! Вам " << age << " лет." << std::endl;
return 0 ;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Форматирование вывода
#include <iostream>
#include <iomanip>
void demonstrateFormatting () {
double pi = 3.14159265359 ;
int number = 42 ;
std::cout << std::fixed << std::setprecision (2 ) << pi << std::endl;
std::cout << std::setw (10 ) << number << std::endl;
std::cout << std::left << std::setw (10 ) << "Hello" << "World" << std::endl;
std::cout << std::hex << number << std::endl;
std::cout << std::oct << number << std::endl;
std::cout << std::dec << number << std::endl;
bool flag = true ;
std::cout << std::boolalpha << flag << std::endl;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Чтение различных типов данных
#include <iostream>
#include <string>
#include <vector>
class ConsoleReader {
public :
static int readInt (const std::string& prompt) {
int value;
std::cout << prompt;
while (!(std::cin >> value)) {
std::cin.clear ();
std::cin.ignore (std::numeric_limits<std::streamsize>::max (), '\n' );
std::cout << "Неверный ввод. Попробуйте снова: " ;
}
std::cin.ignore (std::numeric_limits<std::streamsize>::max (), '\n' );
return value;
}
static double readDouble (const std::string& prompt) {
double value;
std::cout << prompt;
while (!(std::cin >> value)) {
std::cin.clear ();
std::cin.ignore (std::numeric_limits<std::streamsize>::max (), '\n' );
std::cout << "Неверный ввод. Попробуйте снова: " ;
}
std::cin.ignore (std::numeric_limits<std::streamsize>::max (), '\n' );
return value;
}
static std::string readString (const std::string& prompt) {
std::cout << prompt;
std::string value;
std::getline (std::cin, value);
return value;
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Работа с файлами в C++
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Базовые операции с файлами
#include <fstream>
#include <iostream>
#include <string>
class FileManager {
public :
static bool writeToFile (const std::string& filename, const std::string& content) {
std::ofstream file (filename) ;
if (!file.is_open ()) {
std::cerr << "Не удалось открыть файл для записи: " << filename << std::endl;
return false ;
}
file << content;
return file.good ();
}
static std::string readFromFile (const std::string& filename) {
std::ifstream file (filename) ;
if (!file.is_open ()) {
std::cerr << "Не удалось открыть файл для чтения: " << filename << std::endl;
return "" ;
}
std::string content ((std::istreambuf_iterator<char >(file)),
std::istreambuf_iterator<char >()) ;
return content;
}
static bool appendToFile (const std::string& filename, const std::string& content) {
std::ofstream file (filename, std::ios::app) ;
if (!file.is_open ()) {
std::cerr << "Не удалось открыть файл для добавления: " << filename << std::endl;
return false ;
}
file << content;
return file.good ();
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Режимы открытия файлов
#include <fstream>
void demonstrateFileModes () {
std::ofstream out1 ("file1.txt" , std::ios::out) ;
std::ofstream out2 ("file2.txt" , std::ios::app) ;
std::ofstream out3 ("file3.bin" , std::ios::binary) ;
std::fstream io ("file4.txt" , std::ios::in | std::ios::out) ;
std::ofstream out4 ("file5.txt" , std::ios::out | std::ios::trunc) ;
std::fstream io2 ("file6.txt" , std::ios::in | std::ios::out | std::ios::ate) ;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Позиционирование в файле
#include <fstream>
#include <iostream>
class FilePositionManager {
public :
static void demonstratePositioning () {
std::fstream file ("example.txt" , std::ios::in | std::ios::out | std::ios::trunc) ;
if (!file.is_open ()) {
std::cerr << "Не удалось открыть файл" << std::endl;
return ;
}
file << "Hello, World! This is a test file." ;
file.seekg (0 , std::ios::beg);
char buffer[6 ] = {0 };
file.read (buffer, 5 );
std::cout << "Первые 5 символов: " << buffer << std::endl;
file.seekg (7 , std::ios::beg);
file.read (buffer, 5 );
std::cout << "Символы с 7 по 11: " << buffer << std::endl;
std::streampos pos = file.tellg ();
std::cout << "Текущая позиция: " << pos << std::endl;
file.seekg (-10 , std::ios::end);
file.read (buffer, 5 );
std::cout << "Символы за 10 позиций от конца: " << buffer << std::endl;
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Классы и интерфейсы потоков
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Базовые классы потоков
#include <iostream>
#include <sstream>
#include <fstream>
class StreamHierarchyDemo {
public :
static void readFromStream (std::istream& stream) {
std::string line;
while (std::getline (stream, line)) {
std::cout << "Прочитано: " << line << std::endl;
}
}
static void writeToStream (std::ostream& stream, const std::string& data) {
stream << data << std::endl;
}
static void demonstrateHierarchy () {
std::cout << "=== Работа с консолью ===" << std::endl;
writeToStream (std::cout, "Hello from console!" );
std::cout << "=== Работа со строкой ===" << std::endl;
std::stringstream ss;
writeToStream (ss, "Hello from string stream!" );
std::cout << "Содержимое строкового потока: " << ss.str () << std::endl;
std::cout << "=== Работа с файлом ===" << std::endl;
std::ofstream file ("test.txt" ) ;
writeToStream (file, "Hello from file stream!" );
file.close ();
std::ifstream infile ("test.txt" ) ;
readFromStream (infile);
infile.close ();
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Пользовательские манипуляторы
#include <iostream>
#include <iomanip>
struct hex_format {
int value;
hex_format (int v) : value (v) {}
};
std::ostream& operator <<(std::ostream& os, const hex_format& hf) {
return os << "0x" << std::hex << std::uppercase << std::setw (4 )
<< std::setfill ('0' ) << hf.value << std::dec;
}
struct timestamp {
std::chrono::system_clock::time_point time;
timestamp (std::chrono::system_clock::time_point t = std::chrono::system_clock::now ())
: time (t) {}
};
std::ostream& operator <<(std::ostream& os, const timestamp& ts) {
auto time_t = std::chrono::system_clock::to_time_t (ts.time);
os << std::put_time (std::localtime (&time_t ), "%Y-%m-%d %H:%M:%S" );
return os;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Байтовые и символьные потоки
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Работа с бинарными данными
#include <fstream>
#include <vector>
#include <cstdint>
class BinaryStreamManager {
public :
struct DataRecord {
uint32_t id;
double value;
char name[32 ];
};
static bool writeBinaryData (const std::string& filename,
const std::vector<DataRecord>& records) {
std::ofstream file (filename, std::ios::binary) ;
if (!file.is_open ()) {
return false ;
}
size_t count = records.size ();
file.write (reinterpret_cast <const char *>(&count), sizeof (count));
for (const auto & record : records) {
file.write (reinterpret_cast <const char *>(&record), sizeof (record));
}
return file.good ();
}
static std::vector<DataRecord> readBinaryData (const std::string& filename) {
std::vector<DataRecord> records;
std::ifstream file (filename, std::ios::binary) ;
if (!file.is_open ()) {
return records;
}
size_t count;
file.read (reinterpret_cast <char *>(&count), sizeof (count));
records.reserve (count);
for (size_t i = 0 ; i < count; ++i) {
DataRecord record;
file.read (reinterpret_cast <char *>(&record), sizeof (record));
records.push_back (record);
}
return records;
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Преобразование между текстовыми и бинарными данными
#include <sstream>
#include <iomanip>
class DataConverter {
public :
static std::string toHex (const std::vector<uint8_t >& data) {
std::ostringstream oss;
oss << std::hex << std::setfill ('0' );
for (uint8_t byte : data) {
oss << std::setw (2 ) << static_cast <int >(byte) << " " ;
}
return oss.str ();
}
static std::vector<uint8_t > fromHex (const std::string& hexStr) {
std::vector<uint8_t > result;
std::istringstream iss (hexStr) ;
std::string byteStr;
while (iss >> byteStr) {
if (byteStr.length () == 2 ) {
uint8_t byte = static_cast <uint8_t >(std::stoi (byteStr, nullptr , 16 ));
result.push_back (byte);
}
}
return result;
}
static bool readBinarySafe (std::istream& stream, void * buffer, size_t size) {
stream.read (static_cast <char *>(buffer), size);
return stream.good () && stream.gcount () == static_cast <std::streamsize>(size);
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Буферизированные потоки
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Настройка буферизации
#include <fstream>
#include <iostream>
class BufferManager {
public :
static void demonstrateBuffering () {
std::ofstream unbuffered ("unbuffered.txt" ) ;
unbuffered.setf (std::ios::unitbuf);
std::ofstream manual ("manual.txt" ) ;
char buffer[1024 ];
manual.rdbuf ()->pubsetbuf (buffer, sizeof (buffer));
std::streambuf* buf = manual.rdbuf ();
std::cout << "Размер буфера: " << buf->pubseekoff (0 , std::ios::cur, std::ios::out) << std::endl;
manual << "Данные для записи" ;
manual.flush ();
manual.sync ();
}
class CustomBuffer {
private :
static constexpr size_t BUFFER_SIZE = 8192 ;
char buffer[BUFFER_SIZE];
std::ofstream& stream;
public :
explicit CustomBuffer (std::ofstream& s) : stream(s) {
stream.rdbuf ()->pubsetbuf (buffer, BUFFER_SIZE);
}
void flush () {
stream.flush ();
}
};
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Производительность буферизированного ввода/вывода
#include <fstream>
#include <chrono>
#include <vector>
#include <iostream>
class PerformanceTester {
public :
static void compareIOPerformance () {
const size_t DATA_SIZE = 1024 * 1024 ;
std::vector<char > data (DATA_SIZE, 'A' ) ;
auto start = std::chrono::high_resolution_clock::now ();
testUnbufferedWrite (data, "unbuffered.dat" );
auto end = std::chrono::high_resolution_clock::now ();
auto unbufferedTime = std::chrono::duration_cast <std::chrono::milliseconds>(end - start);
start = std::chrono::high_resolution_clock::now ();
testBufferedWrite (data, "buffered.dat" );
end = std::chrono::high_resolution_clock::now ();
auto bufferedTime = std::chrono::duration_cast <std::chrono::milliseconds>(end - start);
std::cout << "Не буферизированная запись: " << unbufferedTime.count () << " мс" << std::endl;
std::cout << "Буферизированная запись: " << bufferedTime.count () << " мс" << std::endl;
std::cout << "Ускорение: " << static_cast <double >(unbufferedTime.count ()) / bufferedTime.count () << "x" << std::endl;
}
private :
static void testUnbufferedWrite (const std::vector<char >& data, const std::string& filename) {
std::ofstream file (filename, std::ios::binary) ;
file.setf (std::ios::unitbuf);
for (char byte : data) {
file.write (&byte, 1 );
}
}
static void testBufferedWrite (const std::vector<char >& data, const std::string& filename) {
std::ofstream file (filename, std::ios::binary) ;
file.write (data.data (), data.size ());
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Потоки в Qt
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
QFile и QTextStream
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <QCoreApplication>
class QtFileManager {
public :
static bool writeTextFile (const QString& filename, const QString& content) {
QFile file (filename) ;
if (!file.open (QIODevice::WriteOnly | QIODevice::Text)) {
qDebug () << "Не удалось открыть файл для записи:" << file.errorString ();
return false ;
}
QTextStream stream (&file) ;
stream.setCodec ("UTF-8" );
stream << content;
file.close ();
return true ;
}
static QString readTextFile (const QString& filename) {
QFile file (filename) ;
if (!file.open (QIODevice::ReadOnly | QIODevice::Text)) {
qDebug () << "Не удалось открыть файл для чтения:" << file.errorString ();
return QString ();
}
QTextStream stream (&file) ;
stream.setCodec ("UTF-8" );
QString content = stream.readAll ();
file.close ();
return content;
}
static bool appendToFile (const QString& filename, const QString& content) {
QFile file (filename) ;
if (!file.open (QIODevice::Append | QIODevice::Text)) {
qDebug () << "Не удалось открыть файл для добавления:" << file.errorString ();
return false ;
}
QTextStream stream (&file) ;
stream.setCodec ("UTF-8" );
stream << content;
file.close ();
return true ;
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Работа с бинарными данными в Qt
#include <QFile>
#include <QDataStream>
#include <QVector>
#include <QDebug>
class QtBinaryManager {
public :
struct NetworkPacket {
quint32 id;
quint32 timestamp;
QString sender;
QByteArray data;
friend QDataStream& operator <<(QDataStream& stream, const NetworkPacket& packet) {
stream << packet.id << packet.timestamp << packet.sender << packet.data;
return stream;
}
friend QDataStream& operator >>(QDataStream& stream, NetworkPacket& packet) {
stream >> packet.id >> packet.timestamp >> packet.sender >> packet.data;
return stream;
}
};
static bool writeBinaryData (const QString& filename, const QVector<NetworkPacket>& packets) {
QFile file (filename) ;
if (!file.open (QIODevice::WriteOnly)) {
qDebug () << "Не удалось открыть файл для записи:" << file.errorString ();
return false ;
}
QDataStream stream (&file) ;
stream.setVersion (QDataStream::Qt_6_0);
stream << static_cast <quint32>(packets.size ());
for (const auto & packet : packets) {
stream << packet;
}
file.close ();
return true ;
}
static QVector<NetworkPacket> readBinaryData (const QString& filename) {
QVector<NetworkPacket> packets;
QFile file (filename) ;
if (!file.open (QIODevice::ReadOnly)) {
qDebug () << "Не удалось открыть файл для чтения:" << file.errorString ();
return packets;
}
QDataStream stream (&file) ;
stream.setVersion (QDataStream::Qt_6_0);
quint32 count;
stream >> count;
packets.reserve (count);
for (quint32 i = 0 ; i < count; ++i) {
NetworkPacket packet;
stream >> packet;
packets.append (packet);
}
file.close ();
return packets;
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Потоковая обработка больших файлов в Qt
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <QCoreApplication>
class LargeFileProcessor {
public :
static bool processLargeTextFile (const QString& inputFilename,
const QString& outputFilename,
std::function<QString(const QString&)> processor) {
QFile inputFile (inputFilename) ;
QFile outputFile (outputFilename) ;
if (!inputFile.open (QIODevice::ReadOnly | QIODevice::Text)) {
qDebug () << "Не удалось открыть входной файл:" << inputFile.errorString ();
return false ;
}
if (!outputFile.open (QIODevice::WriteOnly | QIODevice::Text)) {
qDebug () << "Не удалось открыть выходной файл:" << outputFile.errorString ();
inputFile.close ();
return false ;
}
QTextStream inputStream (&inputFile) ;
QTextStream outputStream (&outputFile) ;
inputStream.setCodec ("UTF-8" );
outputStream.setCodec ("UTF-8" );
qint64 lineCount = 0 ;
while (!inputStream.atEnd ()) {
QString line = inputStream.readLine ();
QString processedLine = processor (line);
outputStream << processedLine << "\n" ;
lineCount++;
if (lineCount % 10000 == 0 ) {
qDebug () << "Обработано строк:" << lineCount;
QCoreApplication::processEvents ();
}
}
inputFile.close ();
outputFile.close ();
qDebug () << "Обработка завершена. Всего строк:" << lineCount;
return true ;
}
static qint64 countLines (const QString& filename) {
QFile file (filename) ;
if (!file.open (QIODevice::ReadOnly | QIODevice::Text)) {
return -1 ;
}
QTextStream stream (&file) ;
stream.setCodec ("UTF-8" );
qint64 count = 0 ;
while (!stream.atEnd ()) {
stream.readLine ();
count++;
}
file.close ();
return count;
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Работа с каталогами
std::filesystem (C++17) — кроссплатформенная библиотека для работы с файловой системой:
Компонент
Назначение
std::filesystem::path
Представление пути к файлу/каталогу
std::filesystem::directory_entry
Элемент каталога (файл или подкаталог)
std::filesystem::directory_iterator
Итератор по содержимому каталога
std::filesystem::recursive_directory_iterator
Рекурсивный обход вложенных каталогов
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main () {
fs::path dir = "network_app" ;
if (fs::exists (dir)) {
std::cout << "Каталог '" << dir << "' существует.\n" ;
for (const auto & entry : fs::directory_iterator (dir)) {
std::cout << " " << entry.path ().filename ()
<< (entry.is_directory () ? " [DIR]" : "" )
<< " — " << fs::file_size (entry) << " байт\n" ;
}
} else {
fs::create_directories (dir / "config" );
std::cout << "Создан каталог: " << fs::absolute (dir) << "\n" ;
}
return 0 ;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Работа с каталогами в Qt — класс QDir
Основные методы QDir :
Метод
Назначение
exists()
Проверка существования каталога
mkdir() / mkpath()
Создание каталога (одного / вложенного)
entryList()
Список файлов и подкаталогов с фильтрацией
rename()
Переименование или перемещение
remove()
Удаление файла; removeRecursively() — каталога
#include <QDir>
#include <QDebug>
#include <QCoreApplication>
void listFilteredFiles (const QString& dirPath) {
QDir dir (dirPath) ;
QStringList filters;
filters << "*.conf" << "*.cfg" << "*.ini" ;
dir.setNameFilters (filters);
QFileInfoList files = dir.entryInfoList (
QDir::Files | QDir::Readable,
QDir::Name
);
qDebug () << "Файлы конфигурации в" << dirPath << ":" ;
for (const QFileInfo& fi : files) {
qDebug () << " " << fi.fileName () << fi.size () << "байт" ;
}
QDir ().mkpath (dirPath + "/certificates" );
QDir ().mkpath (dirPath + "/logs" );
qDebug () << "Структура каталогов создана." ;
}
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Практический пример: конфигурация сетевого приложения
config/
├── server.conf
├── certificates/
│ ├── cert.pem
│ └── key.pem
└── logs/
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QDebug>
class ConfigManager {
QString basePath;
public :
explicit ConfigManager (const QString& base)
: basePath(base) { }
bool initStructure () {
QDir dir;
if (!dir.mkpath (basePath + "/certificates" )) {
qDebug () << "Ошибка создания certificates/" ;
return false ;
}
if (!dir.mkpath (basePath + "/logs" )) {
qDebug () << "Ошибка создания logs/" ;
return false ;
}
QFile confFile (basePath + "/server.conf" ) ;
if (!confFile.exists ()) {
if (!confFile.open (QIODevice::WriteOnly | QIODevice::Text))
return false ;
QTextStream out (&confFile) ;
out << "host=0.0.0.0\n"
<< "port=8080\n"
<< "max_connections=100\n"
<< "cert=" << (basePath + "/certificates/cert.pem" ) << "\n"
<< "key=" << (basePath + "/certificates/key.pem" ) << "\n" ;
confFile.close ();
qDebug () << "Создан server.conf по умолчанию." ;
}
return true ;
}
QMap<QString, QString> loadConfig () {
QMap<QString, QString> settings;
QFile file (basePath + "/server.conf" ) ;
if (!file.open (QIODevice::ReadOnly | QIODevice::Text))
return settings;
QTextStream in (&file) ;
while (!in.atEnd ()) {
QString line = in.readLine ().trimmed ();
if (line.isEmpty () || line.startsWith ('#' ))
continue ;
int sep = line.indexOf ('=' );
if (sep > 0 )
settings[line.left (sep)] = line.mid (sep + 1 );
}
file.close ();
return settings;
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Сериализация
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Сериализация объектов в C++
#include <fstream>
#include <string>
#include <vector>
#include <memory>
class Serializable {
public :
virtual ~Serializable () = default ;
virtual void serialize (std::ostream& stream) const = 0 ;
virtual void deserialize (std::istream& stream) = 0 ;
};
class NetworkMessage : public Serializable {
private :
std::string sender_;
std::string receiver_;
std::string content_;
uint64_t timestamp_;
public :
NetworkMessage () : timestamp_ (0 ) {}
NetworkMessage (const std::string& sender, const std::string& receiver,
const std::string& content)
: sender_ (sender), receiver_ (receiver), content_ (content) {
timestamp_ = std::chrono::duration_cast <std::chrono::milliseconds>(
std::chrono::system_clock::now ().time_since_epoch ()).count ();
}
void serialize (std::ostream& stream) const override {
size_t senderSize = sender_.size ();
size_t receiverSize = receiver_.size ();
size_t contentSize = content_.size ();
stream.write (reinterpret_cast <const char *>(&senderSize), sizeof (senderSize));
stream.write (sender_.data (), senderSize);
stream.write (reinterpret_cast <const char *>(&receiverSize), sizeof (receiverSize));
stream.write (receiver_.data (), receiverSize);
stream.write (reinterpret_cast <const char *>(&contentSize), sizeof (contentSize));
stream.write (content_.data (), contentSize);
stream.write (reinterpret_cast <const char *>(×tamp_), sizeof (timestamp_));
}
void deserialize (std::istream& stream) override {
size_t senderSize;
stream.read (reinterpret_cast <char *>(&senderSize), sizeof (senderSize));
sender_.resize (senderSize);
stream.read (&sender_[0 ], senderSize);
size_t receiverSize;
stream.read (reinterpret_cast <char *>(&receiverSize), sizeof (receiverSize));
receiver_.resize (receiverSize);
stream.read (&receiver_[0 ], receiverSize);
size_t contentSize;
stream.read (reinterpret_cast <char *>(&contentSize), sizeof (contentSize));
content_.resize (contentSize);
stream.read (&content_[0 ], contentSize);
stream.read (reinterpret_cast <char *>(×tamp_), sizeof (timestamp_));
}
const std::string& getSender () const { return sender_; }
const std::string& getReceiver () const { return receiver_; }
const std::string& getContent () const { return content_; }
uint64_t getTimestamp () const { return timestamp_; }
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Сериализация в Qt с использованием QDataStream
#include <QObject>
#include <QByteArray>
#include <QDataStream>
#include <QFile>
#include <QDebug>
class SerializableObject : public QObject {
Q_OBJECT
Q_PROPERTY (QString name READ name WRITE setName)
Q_PROPERTY (int id READ id WRITE setId)
Q_PROPERTY (QByteArray data READ data WRITE setData)
private :
QString name_;
int id_;
QByteArray data_;
public :
explicit SerializableObject (QObject* parent = nullptr )
: QObject(parent), id_(0 ) { }
QString name () const { return name_; }
void setName (const QString& name) { name_ = name; }
int id () const { return id_; }
void setId (int id) { id_ = id; }
QByteArray data () const { return data_; }
void setData (const QByteArray& data) { data_ = data; }
QByteArray toByteArray () const {
QByteArray byteArray;
QDataStream stream (&byteArray, QIODevice::WriteOnly) ;
stream.setVersion (QDataStream::Qt_6_0);
stream << name_ << id_ << data_;
return byteArray;
}
bool fromByteArray (const QByteArray& byteArray) {
if (byteArray.isEmpty ()) {
return false ;
}
QDataStream stream (byteArray) ;
stream.setVersion (QDataStream::Qt_6_0);
stream >> name_ >> id_ >> data_;
return stream.status () == QDataStream::Ok;
}
bool saveToFile (const QString& filename) const {
QFile file (filename) ;
if (!file.open (QIODevice::WriteOnly)) {
qDebug () << "Не удалось открыть файл для записи:" << file.errorString ();
return false ;
}
QDataStream stream (&file) ;
stream.setVersion (QDataStream::Qt_6_0);
stream << QString ("SerializableObject" );
stream << quint32 (1 );
stream << name_ << id_ << data_;
file.close ();
return true ;
}
bool loadFromFile (const QString& filename) {
QFile file (filename) ;
if (!file.open (QIODevice::ReadOnly)) {
qDebug () << "Не удалось открыть файл для чтения:" << file.errorString ();
return false ;
}
QDataStream stream (&file) ;
stream.setVersion (QDataStream::Qt_6_0);
QString className;
quint32 version;
stream >> className >> version;
if (className != "SerializableObject" || version != 1 ) {
qDebug () << "Неверный формат файла" ;
file.close ();
return false ;
}
stream >> name_ >> id_ >> data_;
file.close ();
return stream.status () == QDataStream::Ok;
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Преимущества потоковой обработки
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Эффективность и производительность
#include <iostream>
#include <fstream>
#include <sstream>
#include <chrono>
#include <vector>
#include <algorithm>
class StreamAdvantagesDemo {
public :
static void demonstrateEfficiency () {
const size_t ITERATIONS = 1000000 ;
auto stringStreamTime = measureStringStreamPerformance (ITERATIONS);
auto stringConcatTime = measureStringConcatenationPerformance (ITERATIONS);
std::cout << "StringStream время: " << stringStreamTime << " мс" << std::endl;
std::cout << "Конкатенация строк время: " << stringConcatTime << " мс" << std::endl;
std::cout << "Ускорение: " << static_cast <double >(stringConcatTime) / stringStreamTime << "x" << std::endl;
demonstrateLargeDataProcessing ();
}
private :
static long long measureStringStreamPerformance (size_t iterations) {
auto start = std::chrono::high_resolution_clock::now ();
for (size_t i = 0 ; i < iterations; ++i) {
std::ostringstream oss;
oss << "Iteration " << i << ": value=" << (i * 2 ) << ", result=" << (i % 100 );
std::string result = oss.str ();
}
auto end = std::chrono::high_resolution_clock::now ();
return std::chrono::duration_cast <std::chrono::milliseconds>(end - start).count ();
}
static long long measureStringConcatenationPerformance (size_t iterations) {
auto start = std::chrono::high_resolution_clock::now ();
for (size_t i = 0 ; i < iterations; ++i) {
std::string result = "Iteration " + std::to_string (i) +
": value=" + std::to_string (i * 2 ) +
", result=" + std::to_string (i % 100 );
}
auto end = std::chrono::high_resolution_clock::now ();
return std::chrono::duration_cast <std::chrono::milliseconds>(end - start).count ();
}
static void demonstrateLargeDataProcessing () {
const size_t FILE_SIZE = 100 * 1024 * 1024 ;
createLargeFile ("large_data.txt" , FILE_SIZE);
auto streamTime = processWithStreams ("large_data.txt" );
std::cout << "Обработка 100 МБ файла потоками: " << streamTime << " мс" << std::endl;
std::remove ("large_data.txt" );
}
static void createLargeFile (const std::string& filename, size_t size) {
std::ofstream file (filename) ;
const std::string line = "This is a test line for large file processing.\n" ;
for (size_t i = 0 ; i < size / line.length (); ++i) {
file << line;
}
}
static long long processWithStreams (const std::string& filename) {
auto start = std::chrono::high_resolution_clock::now ();
std::ifstream file (filename) ;
std::string line;
size_t lineCount = 0 ;
size_t charCount = 0 ;
while (std::getline (file, line)) {
lineCount++;
charCount += line.length ();
}
auto end = std::chrono::high_resolution_clock::now ();
return std::chrono::duration_cast <std::chrono::milliseconds>(end - start).count ();
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Гибкость и расширяемость
#include <iostream>
#include <fstream>
#include <sstream>
#include <memory>
#include <vector>
class ProcessingStream {
public :
virtual ~ProcessingStream () = default ;
virtual void process (const std::string& data) = 0 ;
virtual void flush () = 0 ;
};
class LoggingStream : public ProcessingStream {
private :
std::ofstream logFile_;
public :
explicit LoggingStream (const std::string& filename)
: logFile_(filename, std::ios::app) { }
void process (const std::string& data) override {
logFile_ << "[LOG] " << data << std::endl;
}
void flush () override {
logFile_.flush ();
}
};
class EncryptionStream : public ProcessingStream {
private :
std::unique_ptr<ProcessingStream> nextStream_;
int shift_;
public :
EncryptionStream (std::unique_ptr<ProcessingStream> next, int shift = 3 )
: nextStream_ (std::move (next)), shift_ (shift) {}
void process (const std::string& data) override {
std::string encrypted;
for (char c : data) {
encrypted += static_cast <char >(c + shift_);
}
nextStream_->process (encrypted);
}
void flush () override {
nextStream_->flush ();
}
};
class CompressionStream : public ProcessingStream {
private :
std::unique_ptr<ProcessingStream> nextStream_;
public :
CompressionStream (std::unique_ptr<ProcessingStream> next)
: nextStream_ (std::move (next)) {}
void process (const std::string& data) override {
std::string compressed;
for (size_t i = 0 ; i < data.length (); ) {
char current = data[i];
size_t count = 1 ;
while (i + count < data.length () && data[i + count] == current && count < 255 ) {
count++;
}
compressed += static_cast <char >(count);
compressed += current;
i += count;
}
nextStream_->process (compressed);
}
void flush () override {
nextStream_->flush ();
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Цепочка обработки потоков
class StreamChainDemo {
public :
static void demonstrateChainProcessing () {
auto logger = std::make_unique <LoggingStream>("processed.log" );
auto encryptor = std::make_unique <EncryptionStream>(std::move (logger), 5 );
auto compressor = std::make_unique <CompressionStream>(std::move (encryptor));
std::vector<std::string> testData = {
"Hello World! This is a test message." ,
"AAAAABBBBCCCDDEEF" ,
"Another test message with different content."
};
for (const auto & data : testData) {
compressor->process (data);
}
compressor->flush ();
std::cout << "Обработка данных через цепочку потоков завершена." << std::endl;
std::cout << "Результаты сохранены в processed.log" << std::endl;
}
};
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Заключение
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Ключевые преимущества потоков ввода/вывода
Унификация интерфейса - единый способ работы с различными источниками данных
Безопасность типов - компилятор контролирует корректность операций
Автоматическая обработка ошибок - исключения и коды состояния
Гибкость и расширяемость - легкость в добавлении новых типов потоков
Производительность - оптимизированная буферизация и обработка
Совместимость - стандартизированный интерфейс across различных платформ
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Лучшие практики
Используйте RAII - автоматическое управление ресурсами
Проверяйте состояние потоков - всегда проверяйте результат операций
Обрабатывайте исключения - корректная обработка ошибок
Используйте буферизацию - для улучшения производительности
Закрывайте потоки - явное закрытие при завершении работы
Используйте современные возможности C++ - умные указатели и автоматическое управление
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Интеграция с Qt
QFile и QTextStream для текстовых файлов
QDataStream для бинарных данных и сериализации
QBuffer для работы с данными в памяти
QTcpSocket и QUdpSocket для сетевых потоков
Сигналы и слоты для асинхронной обработки
Потоки ввода/вывода и работа с файлами
Программирование сетевых приложений
Вопросы для самопроверки
Какие основные классы используются для работы с файлами в C++?
Чем отличается текстовый режим от бинарного при работе с файлами?
Как реализовать безопасное чтение данных из файла?
Какие преимущества дает использование потоков в Qt?
Как организовать эффективную работу с большими файлами?
Что такое сериализация и где она применяется?
Как обеспечить потокобезопасность при работе с файлами?
Какие существуют методы оптимизации производительности I/O операций?
Как реализовать пользовательские манипуляторы для потоков?
Как обрабатывать ошибки при работе с потоками ввода/вывода?
Потоки ввода/вывода и работа с файлами
Заметки докладчика:
- Потоки ввода-вывода — фундамент для сетевого программирования: чтение конфигурационных файлов, сериализация данных для передачи по сети, логирование активности серверов и клиентов.
- Это самая объёмная лекция курса. Планируйте время: не менее 2 пар. Рекомендуется разбить на две части: C++ потоки и Qt потоки.
- Упомяните, что концепция потоков (streams) используется практически во всех сетевых протоколах — TCP-соединение абстрактно представляет собой двунаправленный поток байтов.
Заметки докладчика:
- Ключевые классы, которые нужно запомнить: ifstream/ofstream для файлов, istringstream/ostringstream для строковых манипуляций (парсинг, форматирование).
- В Qt аналоги: QTextStream — для текста, QDataStream — для бинарных данных с автоматическим управлением порядком байтов.
- Важный момент: все классы наследуются от ios_base, поэтому флаги форматирования (hex, fixed и др.) едины для всех потоков.
- Полиморфизм потоков: функция, принимающая std::istream&, может работать с файлом, строкой или консолью — это используется при написании тестов (подменяем файл на строковый поток).
Заметки докладчика:
- std::getline(cin, str) читает всю строку до '\n', в отличие от operator>>, который останавливается на пробеле. Это критически важно при чтении строк после числового ввода — в буфере остаётся '\n'.
- В сетевом программировании getline используется для чтения строк протоколов: HTTP-заголовки (строка за строкой), SMTP-команды, IRC-сообщения.
- Покажите типичную ошибку: cin >> age; getline(cin, name); — getline прочитает пустую строку из-за остатка '\n' в буфере. Решение: cin.ignore() перед getline.
Заметки докладчика:
- Форматирование важно для логирования сетевой активности: std::fixed + std::setprecision — для вывода временных меток (rtt, timeout), std::hex — для отображения дампов бинарных протоколов (пакетных данных).
- std::setw + std::setfill полезны для табличного вывода таблиц маршрутизации, таблиц ARP, логов соединений.
- Манипуляторы «липкие» (sticky): std::hex, std::fixed, std::left действуют до следующего изменения. std::setw — «нелипкий», применяется только к следующему выводу.
- В Qt аналоги: QTextStream::setFieldWidth(), setPadChar(), setIntegerBase(), setRealNumberPrecision().
Заметки докладчика:
- Бинарная сериализация используется при передаче структурированных данных по сети. Обратите внимание на sizeof и выравнивание структур — это частый источник ошибок при межплатформенной передаче.
- Сетевой порядок байтов (network byte order) — big-endian. Если платформа little-endian (x86/x64), необходимо преобразование: htonl/htons/ntohl/ntohs. В C++20: std::endian.
- Класс DataRecord с char name[32] — пример фиксированного размера. В реальных сетевых протоколах строки часто имеют префикс длины (length-prefixed), как в примере NetworkMessage дальше.
- QDataStream автоматически обрабатывает порядок байтов (по умолчанию big-endian) и длину строк — это значительно упрощает сетевое программирование в Qt.
Заметки докладчика:
- QFile — основной класс Qt для файловых операций. Работает как с локальными, так и с сетевыми ресурсами (через QNetworkAccessManager).
- QTextStream автоматически определяет кодировку (UTF-8 по умолчанию в Qt 6), обрабатывает BOM, преобразует окончания строк (\r\n, \r, \n).
- QDataStream — для бинарной сериализации: записывает/читает типы Qt (QString, QByteArray, QVector и др.) с метаданными (длина строки, тип). Важно: всегда устанавливать версию setVersion(QDataStream::Qt_6_0) для совместимости.
- Практический совет: для сетевой передачи используйте QDataStream поверх QTcpSocket — получите готовую сериализацию с контролем порядка байтов.
Заметки докладчика:
- Работа с каталогами — обязательный пункт учебной программы (тема 6).
- std::filesystem (C++17) — современный кроссплатформенный API для работы с файловой системой. Предпочтителен для нового кода.
- QDir — Qt-аналог, имеет больше удобных методов (entryList с фильтрами, nameFilters).
- Для сетевых приложений: типичная задача — чтение конфигурации из директории, управление лог-файлами, работа с сертификатами.
- QDir::separator() возвращает правильный разделитель для платформы (/ или \).
Заметки докладчика:
- Сериализация — преобразование объекта в последовательность байтов для хранения или передачи по сети. Перед отправкой по сокету данные всегда сериализуются.
- Два основных подхода: бинарная сериализация (компактно, быстро, но нечитаемо и хрупко при изменении формата) и текстовая/JSON (читаемо, самодокументируемо, но больше размер и медленнее).
- В сетевом программировании: Protocol Buffers (Google) — эффективная бинарная сериализация с описанием схемы (.proto); JSON — для REST API; MessagePack — бинарный JSON-подобный формат.
- Ключевое правило: всегда указывайте версию формата (как в примере SerializableObject с quint32 version) — это позволяет эволюционировать протокол без поломки совместимости.
Заметки докладчика:
1. ifstream, ofstream, fstream — для текстовых и бинарных файлов; istringstream, ostringstream, stringstream — для работы со строками.
2. Текстовый режим преобразует символы новой строки (\n → \r\n на Windows), бинарный (std::ios::binary) записывает данные без преобразования.
3. Всегда проверять is_open() перед чтением, проверять stream.good() после операций, использовать seekg/tellg для контроля позиции, обрабатывать EOF.
4. Единый интерфейс через QFile/QTextStream/QDataStream, автоматическая работа с кодировками, интеграция с сигнально-слотовым механизмом Qt, поддержка версионирования форматов.
5. Построчное чтение (std::getline, readLine()), буферизация (pubsetbuf), использование mmap для очень больших файлов, поточная обработка без загрузки всего файла в память.
6. Сериализация — преобразование объекта в последовательность байтов для хранения или передачи. Применяется: сохранение конфигурации, передача данных по сети, persistence объектов, межпроцессное взаимодействие.
7. Использовать мьютексы (std::mutex) или QLockFile, избегать одновременной записи из нескольких потоков, применять атомарные операции для флагов.
8. Буферизация (большой буфер), пакетная запись вместо посимвольной, асинхронное I/O, memory-mapped файлы, минимизация числа системных вызовов.
9. Определить функцию или объект с перегруженным operator<< для std::ostream, использовать std::setw, std::setfill, std::hex и другие манипуляторы.
10. Проверять флаги состояния (failbit, badbit), использовать exceptions() для автоматического выброса исключений, всегда закрывать файлы через RAII, логировать ошибки.